package com.haha.service.program.impl;

import com.haha.common.ApiResponse;
import com.haha.constant.BaseCode;
import com.haha.domain.qo.SeatInfoQo;
import com.haha.domain.qo.performance.GetSessionsOfPerformanceQo;
import com.haha.domain.vo.SeatInfoVo;
import com.haha.domain.vo.performance.SessionsOfPerformanceVo;
import com.haha.lock.DistributedLock;
import com.haha.mapper.SeatMapper;
import com.haha.mapper.SessionMapper;
import com.haha.mapper.VenueMapper;
import com.haha.redis.RedisKeyEnum;
import com.haha.redis.utils.RedisUtils;
import com.haha.service.program.ProgramService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author zouwz5
 * @date 2025/10/5 18:01
 */
@Service
@Slf4j
public class ProgramServiceImpl implements ProgramService {

    @Autowired
    private SessionMapper sessionMapper;
    @Autowired
    private VenueMapper venueMapper;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private SeatMapper seatMapper;

    private final DistributedLock distributedLock;

    public ProgramServiceImpl(DistributedLock distributedLock) {
        this.distributedLock = distributedLock;
    }

    /**
     * 从数据库查 节目场次信息
     * @param qo
     * @return
     */
    @Override
    public ApiResponse<List<SessionsOfPerformanceVo>> getSessionsOfPerformance(GetSessionsOfPerformanceQo qo) {

        return ApiResponse.ok(sessionMapper.getSessionInfoOfPerformance(qo.getPerformanceId()));
    }

    /**
     * 加入缓存 查询该节目所有场次信息
      * @param qo
     * @return
     */
    @Override
    public ApiResponse<List<SessionsOfPerformanceVo>> getSessionsOfPerformanceV2(GetSessionsOfPerformanceQo qo) {
        Integer performanceId = qo.getPerformanceId();
        if (null == performanceId){
            log.error(BaseCode.PARAMETER_ERROR.getMsg(), "节目id不可为空！");
        }

        // 第一次检查缓存
        List<SessionsOfPerformanceVo> resVo = getSessionsFromCache(performanceId);
        // 缓存缺失
        if (resVo.isEmpty()){
            log.warn("getSessionsOfPerformanceV2 : " + "缓存缺失-节目的场次信息");
            // 双检加锁 回写缓存
            String lockKey = RedisKeyEnum.DISTRIBUTEDLOCK_PROGRAM_SESSION.getKey();
            boolean locked = false;
            try {
                locked = distributedLock.tryLock(lockKey,0,3,TimeUnit.SECONDS);
                if (locked){
                    log.warn("缓存缺失-节目的场次信息-获取分布式锁成功");
                    // 再次检查缓存：a刚刚检查完缓存缺失，b就回写缓存成功了，并且a申请分布式锁时，b刚好释放锁
                    resVo = getSessionsFromCache(performanceId);
                    if (!resVo.isEmpty()){
                        return ApiResponse.ok(resVo);
                    }
                    log.warn("缓存缺失-节目的场次信息-获取分布式锁成功-缓存仍旧缺失，查数据库");
                    //缓存仍旧缺失，查数据库
                    resVo = sessionMapper.getSessionInfoOfPerformance(qo.getPerformanceId());
                    // TODO: 也可以异步回写缓存，这里同步回写
                    addSessionsToCache(resVo, performanceId);
                    return ApiResponse.ok(resVo);
                }else {
                    // 获取锁失败，说明已经有线程去执行回写缓存的任务了
                    try {
                        // 阻塞100ms,
                        Thread.sleep(1000);
                        // 再次检查缓存
                        resVo = getSessionsFromCache(performanceId);
                        if (!resVo.isEmpty()){
                            return ApiResponse.ok(resVo);
                        }else{
                            //缓存仍然缺失，查数据库
                            log.warn("缓存缺失-节目的场次信息-最终降级：直接查询数据库");
                            resVo = sessionMapper.getSessionInfoOfPerformance(qo.getPerformanceId());
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                }
            }finally {
                if (locked){
                    distributedLock.unlock(lockKey);
                }
            }
        }

        return ApiResponse.ok(resVo);
    }



    /**
     * 回写缓存：节目的场次信息
     *
     * @param resVo
     * @param performanceId
     */
    private void addSessionsToCache(List<SessionsOfPerformanceVo> resVo, Integer performanceId) {
        String cacheKey = RedisKeyEnum.PROGRAM_SESSIONS_PREFIX.getKey(performanceId);

        Map<String, Object> sessions = resVo.stream()
                .collect(Collectors.toMap(o -> o.getSessionId().toString(), o -> o));
        // todo: 失效时间未设置
        redisUtils.hmset(cacheKey,sessions);
    }


    /**
     * 从缓存 查询该节目所有场次信息
     * @param performanceId
     * @return
     */
    private List<SessionsOfPerformanceVo> getSessionsFromCache(Integer performanceId) {
        // program:sessions:performanceId
        String cacheKey = RedisKeyEnum.PROGRAM_SESSIONS_PREFIX.getKey(performanceId);

        // 从redis中查场次数据
        Map<Object, Object> sessions = redisUtils.hmget(cacheKey);

        // 缓存缺失
        if(sessions == null || sessions.isEmpty()) {
            return new ArrayList<>();
        }

        List<SessionsOfPerformanceVo> voList = sessions.values().stream().filter(o -> o instanceof SessionsOfPerformanceVo)
                .map(o -> (SessionsOfPerformanceVo) o)
                .collect(Collectors.toList());
        return voList;
    }

    @Override
    public ApiResponse<List<SeatInfoVo>> getSeatInfo(SeatInfoQo qo) {
        List<SeatInfoVo> res = seatMapper.getSeatInfo(qo.getPerformanceId(), qo.getSessionId());
        return ApiResponse.ok(res);
    }
}
